// based on es6-collections by Andrea Giammarchi, @WebReflection
// Object.is shim from the MDN
(function (global) {
	'use strict';

	if (!Object.is) {
		Object.is = function(v1, v2) {
			if (v1 === 0 && v2 === 0) {
				return 1 / v1 === 1 / v2;
			}
			if (v1 !== v1) {
				return v2 !== v2;
			}
			return v1 === v2;
		};
	}

	//shared pointer
	var i;
	//shortcuts
	var defineProperty = Object.defineProperty, is = Object.is;

	if (!global.Set) {
		global.Map = createCollection({
			// WeakMap#delete(key:void*):boolean
			'delete': sharedDelete,
			//:was Map#get(key:void*[, d3fault:void*]):void*
			// Map#has(key:void*):boolean
			has: mapHas,
			// Map#get(key:void*):boolean
			get: sharedGet,
			// Map#set(key:void*, value:void*):void
			set: sharedSet,
			// Map#keys(void):Array === not in specs
			keys: sharedKeys,
			// Map#values(void):Array === not in specs
			values: sharedValues,
			// Map#forEach(callback:Function, context:void*):void ==> callback.call(context, key, value, mapObject) === not in specs`
			forEach: sharedForEach,
			// Map#clear():
			clear: sharedClear
		});

		global.Set = createCollection({
			// Set#has(value:void*):boolean
			has: setHas,
			// Set#add(value:void*):boolean
			add: sharedAdd,
			// Set#delete(key:void*):boolean
			'delete': sharedDelete,
			// Set#clear():
			clear: sharedClear,
			// Set#values(void):Array === not in specs
			values: sharedValues,
			// Set#forEach(callback:Function, context:void*):void ==> callback.call(context, value, index) === not in specs
			forEach: sharedSetIterate
		});
	}

	/**
	 * ES6 collection constructor
	 * @return {Function} a collection class
	 */
	function createCollection(proto, objectOnly){
		function Collection(a){
			if (!this || this.constructor !== Collection) return new Collection(a);
			this._keys = [];
			this._values = [];
			this.objectOnly = objectOnly;

			//parse initial iterable argument passed
			if (a) init.call(this, a);
		}

		//define size for non object-only collections
		if (!objectOnly) {
			defineProperty(proto, 'size', {
				get: sharedSize
			});
		}

		//set prototype
		proto.constructor = Collection;
		Collection.prototype = proto;

		return Collection;
	}


	/** parse initial iterable argument passed */
	function init(a){
		//init Set argument, like `[1,2,3,{}]`
		if (this.add)
			a.forEach(this.add, this);
		//init Map argument like `[[1,2], [{}, 4]]`
		else
			a.forEach(function(a){this.set(a[0],a[1])}, this);
	}


	/** delete */
	function sharedDelete(key) {
		if (this.has(key)) {
			this._keys.splice(i, 1);
			this._values.splice(i, 1);
		}
		// Aurora here does it while Canary doesn't
		return -1 < i;
	}

	function sharedGet(key) {
		return this.has(key) ? this._values[i] : undefined;
	}

	function has(list, key) {
		if (this.objectOnly && key !== Object(key))
			throw new TypeError("Invalid value used as weak collection key");
		//NaN or 0 passed
		if (key != key || key === 0) for (i = list.length; i-- && !is(list[i], key););
		else i = list.indexOf(key);
		return -1 < i;
	}

	function setHas(value) {
		return has.call(this, this._values, value);
	}

	function mapHas(value) {
		return has.call(this, this._keys, value);
	}

	/** @chainable */
	function sharedSet(key, value) {
		this.has(key) ?
			this._values[i] = value
			:
			this._values[this._keys.push(key) - 1] = value
		;
		return this;
	}

	/** @chainable */
	function sharedAdd(value) {
		if (!this.has(value)) this._values.push(value);
		return this;
	}

	function sharedClear() {
		this._values.length = 0;
	}

	/** keys, values, and iterate related methods */
	function sharedValues() {
		return this._values.slice();
	}

	function sharedKeys() {
		return this._keys.slice();
	}

	function sharedSize() {
		return this._values.length;
	}

	function sharedForEach(callback, context) {
		var self = this;
		var values = self._values.slice();
		self._keys.slice().forEach(function (key, n) {
			callback.call(context, values[n], key, self);
		});
	}

	function sharedSetIterate(callback, context) {
		var self = this;
		self._values.slice().forEach(function (value) {
			callback.call(context, value, value, self);
		});
	}
})(typeof(window) !== 'undefined' && window || this);